/**
 * Copyright 2017 orivil Authors. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

// route 包提供了一个路径匹配器。
package route

import (
	"strings"
	"fmt"
	"net/url"
)

const (
	wildcard = "::"
)

type Walk func(node string, isParam bool)

type Path string

func (p Path) GetParams(walk Walk) (path string, params []string, pattern bool) {
	if p == "/" || p == "" {
		path = ""
		return
	}
	if p[len(p)-1] == '/' {
		pattern = true
	}
	path = strings.TrimRight(string(p), "/")
	path = strings.TrimLeft(path, "/")
	nodes := strings.Split(path, "/")
	path = ""
	for _, node := range nodes {
		if !strings.HasPrefix(node, ":") {
			path += "/" + node
			if walk != nil {
				walk(node, false)
			}
		} else {
			node = strings.TrimLeft(node, ":")
			params = append(params, node)
			if walk != nil {
				walk(node, true)
			}
		}
	}
	path = strings.TrimLeft(path, "/")
	return
}

// action 必需返回注册路径，路由器会将 action 注册到相应的路径中。
type Action interface {
	Path() string
}

// 采用 B+ 树结构保存数据。
type trie struct {
	next    map[string]*trie
	param   string
	action  Action
	pattern bool
}

// new next node
func (t *trie) newNode(path string) *trie {
	node := &trie{
		next: make(map[string]*trie, 1),
	}
	t.next[path] = node
	return node
}

type Router struct {
	routes  *trie
}

func NewRouter() *Router {

	return &Router{
		routes: &trie{
			next: make(map[string]*trie, 1),
		},
	}
}

// AddAction 添加 action 到路由器中. 以下3种情况会发生 panic:
//  1. 参数名为空(e.g. "/foo/::/bar")
//  2. 同一节点出现不同的参数名(e.g. "/foo/:cat/bar" "/foo/:dog")
//  3. 重复注册(e.g. "/foo/bar" "/foo/bar")
func (r *Router) AddAction(actions ...Action) {
	for _, act := range actions {
		var path = Path(act.Path())
		current := r.routes
		_, _, pattern := path.GetParams(func(node string, isParam bool) {
			name := node
			var param string
			if isParam {
				name = wildcard
				param = node
				if param == "" {
					panic(fmt.Errorf("route: action path[%s] parameter name is empty", path))
				}
			}
			if next, ok := current.next[name]; ok {
				current = next
				if isParam && next.param != "" && next.param != param {
					panic(fmt.Errorf("route: [%s][%s] the same node must have the same parameter name", path, current.action.Path()))
				}
			} else {
				current = current.newNode(name)
			}
			current.param = param
		})

		if current.action != nil {
			panic(fmt.Errorf("route conflict: [Path: %s] already registered an action", path))
		}
		current.action = act
		current.pattern = pattern
	}
}

// Match 匹配路径，返回 action 及 路径参数。
func (r *Router) Match(path string) (act Action, params url.Values) {
	paths := strings.Split(path, "/")
	current := r.routes
	var next *trie
	var pNode *trie
	var ok bool
	params = url.Values{}
	for _, path := range paths {
		if path == "" {
			continue
		}
		if current.pattern {
			pNode = current
		}
		if next, ok = current.next[path]; ok {
			current = next
		} else if next, ok = current.next[wildcard]; ok {
			current = next
			params.Add(current.param, path)
		} else {
			current = nil
			break
		}
	}

	if current != nil {
		act = current.action
	}
	if act == nil && pNode != nil {
		act = pNode.action
	}
	if len(params) == 0 {
		params = nil
	}
	return
}